Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

using access methods in transport layer #5648

Merged
merged 1 commit into from
Jan 16, 2024

Conversation

mojganii
Copy link
Collaborator

@mojganii mojganii commented Jan 3, 2024

This PR enables the app to utilize different API access methods in response to network errors. Each method is associated with a boolean flag indicating whether it's enabled or not. Following a failed network call, the app switches to the next enabled API access method.

For the subsequent method, the app selects the first enabled method from the list, following the one that was just used (and failed).

Upon reaching the end of the list, the app cycles back to the beginning for the next method.

  • If no method is enabled, the app defaults to using the 'direct' method.

  • In the case where only one method is enabled, the app persists with that method, even if it experienced a recent failure.

Note: Network errors, such as timeouts or dropped connections, trigger this mechanism. It excludes errors that indicate successful communication with the remote machine, such as an HTTP 500 response.


This change is Reviewable

@mojganii mojganii added the iOS Issues related to iOS label Jan 3, 2024
Copy link

linear bot commented Jan 3, 2024

Copy link
Contributor

@buggmagnet buggmagnet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed all commit messages.
Reviewable status: 0 of 17 files reviewed, 4 unresolved discussions (waiting on @mojganii)


ios/MullvadREST/Transport/TransportProvider.swift line 94 at r1 (raw file):

    /// Returns a randomly selected shadowsocks configuration.
    private func makeBridgeConfiguration() throws -> ShadowsocksConfiguration {

We shouldn't be renaming this.
The term bridge has some implication in networks, and a shadowsocks connection is not necessarily used to create bridged networks.
Please revert this to the previous name.


ios/MullvadREST/Transport/TransportStrategy.swift line 19 at r1 (raw file):

        /// Connecting via local Shadowsocks proxy
        case bridge

We don't need this case, since we're using shadowsocks to connect via bridges.
We can just reuse shadowsocks with a local configuration instead of a user defined one.


ios/MullvadREST/Transport/TransportStrategy.swift line 45 at r1 (raw file):

        self.userDefaults = userDefaults
        self.dataSource = datasource
        self.lastReachableConfigurationId = UUID(

There are 2 problems with this

  • UUID(uuidString: "") always returns nil
    So if there is no value for LastReachableConfigurationCacheKey it will fail anyway, and we don't need to construct an invalid UUID for this
  • This class shouldn't select itself how to get a lastReachableConfigurationId, it should be dependency injected to it.

That guarantees that the class is using the parameters provided to it, and will make it easier to debug if something goes wrong.


ios/MullvadREST/Transport/TransportStrategy.swift line 86 at r1 (raw file):

    /// Returning `Direct` if there is no enabled configuration
    private func next() -> PersistentAccessMethod {
        let direct = dataSource.accessMethods.first(where: { $0.kind == .direct })!

We do this a lot, it would probably be useful to add that as an extension in AccessMethodRepositoryDataSource to query it directly.

public extension AccessMethodRepositoryDataSource {
    var directAccess: PersistentAccessMethod {
        accessMethods.first(where: {$0.kind == .direct})!
    }
}

Code quote:

dataSource.accessMethods.first(where: { $0.kind == .direct })!

Copy link
Contributor

@buggmagnet buggmagnet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 0 of 17 files reviewed, 10 unresolved discussions (waiting on @mojganii)


ios/MullvadRESTTests/TransportStrategyTests.swift line 10 at r1 (raw file):

@testable import MullvadREST
import MullvadSettings

import @testable MullvadSettings


ios/MullvadRESTTests/TransportStrategyTests.swift line 33 at r1 (raw file):

    }

    func testContinuesToUseDirectWhenNoOneIsEnabled() {

How about func testDefaultStrategyIsDirectWhenAllMethodsAreDisabled ?

Code quote:

testContinuesToUseDirectWhenNoOneIsEnabled

ios/MullvadRESTTests/TransportStrategyTests.swift line 58 at r1 (raw file):

    }

    func testContinuesToUseBridgeWhenJustOneIsEnabled() {

This test name is misleading IMHO
What we are testing here is that if one method is enabled, it's always selected.
The fact that it's bridges or something else doesn't change that.

I would probably rename this testSameMethodIsReusedIfEverythingElseIsDisabled

Code quote:

testContinuesToUseBridgeWhenJustOneIsEnabled

ios/MullvadRESTTests/TransportStrategyTests.swift line 83 at r1 (raw file):

    }

    func testContinuesToUseDirectWhenItReachesEnd() {

Same as above, this is misleading. direct can be disabled, so what we are really testing here is that we loop over all the enabled methods infinitely when one fails.

To this effect, I propopse that we do the following for this test

  • Disable direct
  • Add 2 more methods , one shadowSocks, one socks5
  • We loop over all the strategies available twice (to make sure we did loop) and we assert the current strategy after each failure is different than the previous one. (We don't need to check the strategy itself, since the next test does that)

It's a big test, but it's important that we get this right. The current version of the test doesn't really test much, and wouldn't detect if the selection was broken to only ever return direct

Also we could probably rename this func testLoopsFromTheStartAfterTryingAllEnabledStrategies


ios/MullvadRESTTests/TransportStrategyTests.swift line 152 at r1 (raw file):

        XCTAssertEqual(
            transportStrategy.connectionTransport(),
            .shadowsocks(configuration: ShadowsocksConfiguration(

We can (and should, for readability) reuse that object across the test by declaring it as class member
Same for the direct and bridges methods, it would make the tests easier to read.


ios/MullvadRESTTests/TransportStrategyTests.swift line 171 at r1 (raw file):

                .cipher == config2.cipher && config1.password == config2.password
        case let (.socks5(config1), .socks5(config2)):
            return config1.address.rawValue == config2.address.rawValue && config1.port == config2.port

AnyIPAddress implements Equatable
No need for the rawValue checks here

Copy link
Collaborator Author

@mojganii mojganii left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 0 of 17 files reviewed, 10 unresolved discussions (waiting on @buggmagnet)


ios/MullvadREST/Transport/TransportProvider.swift line 94 at r1 (raw file):

Previously, buggmagnet wrote…

We shouldn't be renaming this.
The term bridge has some implication in networks, and a shadowsocks connection is not necessarily used to create bridged networks.
Please revert this to the previous name.

I took the part are related to loading/creating local shadow-socks proxy into separate object.while we added custom shadow-socks and socks5, it doesn't seem loading local shadow-socks configuration shall be the responsibility of TransportProvider


ios/MullvadREST/Transport/TransportStrategy.swift line 19 at r1 (raw file):

Previously, buggmagnet wrote…

We don't need this case, since we're using shadowsocks to connect via bridges.
We can just reuse shadowsocks with a local configuration instead of a user defined one.

I put optional for the associated value in case .shadowsocks at TransportStrategy that nil means load shadow-socks proxy with local configuration.


ios/MullvadREST/Transport/TransportStrategy.swift line 45 at r1 (raw file):

Previously, buggmagnet wrote…

There are 2 problems with this

  • UUID(uuidString: "") always returns nil
    So if there is no value for LastReachableConfigurationCacheKey it will fail anyway, and we don't need to construct an invalid UUID for this
  • This class shouldn't select itself how to get a lastReachableConfigurationId, it should be dependency injected to it.

That guarantees that the class is using the parameters provided to it, and will make it easier to debug if something goes wrong.

I couldn't follow the second bullet.do you mean we need to save PersistentAccessMethod data type into UserDefaults instead of casting the string value ofUUID ?


ios/MullvadREST/Transport/TransportStrategy.swift line 86 at r1 (raw file):

Previously, buggmagnet wrote…

We do this a lot, it would probably be useful to add that as an extension in AccessMethodRepositoryDataSource to query it directly.

public extension AccessMethodRepositoryDataSource {
    var directAccess: PersistentAccessMethod {
        accessMethods.first(where: {$0.kind == .direct})!
    }
}

fair enough.I doubt to get it done based on the discussion we had before on how to use extension


ios/MullvadRESTTests/TransportStrategyTests.swift line 10 at r1 (raw file):

Previously, buggmagnet wrote…

import @testable MullvadSettings

done


ios/MullvadRESTTests/TransportStrategyTests.swift line 33 at r1 (raw file):

Previously, buggmagnet wrote…

How about func testDefaultStrategyIsDirectWhenAllMethodsAreDisabled ?

is not it a bit long? how about this :

Code snippet:

testUseDefaultStrategyWhenAllIsDisabled

ios/MullvadRESTTests/TransportStrategyTests.swift line 58 at r1 (raw file):

Previously, buggmagnet wrote…

This test name is misleading IMHO
What we are testing here is that if one method is enabled, it's always selected.
The fact that it's bridges or something else doesn't change that.

I would probably rename this testSameMethodIsReusedIfEverythingElseIsDisabled

how about testReuseSameStrategyWhenEverythingElseIsDisabled


ios/MullvadRESTTests/TransportStrategyTests.swift line 83 at r1 (raw file):

Previously, buggmagnet wrote…

Same as above, this is misleading. direct can be disabled, so what we are really testing here is that we loop over all the enabled methods infinitely when one fails.

To this effect, I propopse that we do the following for this test

  • Disable direct
  • Add 2 more methods , one shadowSocks, one socks5
  • We loop over all the strategies available twice (to make sure we did loop) and we assert the current strategy after each failure is different than the previous one. (We don't need to check the strategy itself, since the next test does that)

It's a big test, but it's important that we get this right. The current version of the test doesn't really test much, and wouldn't detect if the selection was broken to only ever return direct

Also we could probably rename this func testLoopsFromTheStartAfterTryingAllEnabledStrategies

ok


ios/MullvadRESTTests/TransportStrategyTests.swift line 152 at r1 (raw file):

Previously, buggmagnet wrote…

We can (and should, for readability) reuse that object across the test by declaring it as class member
Same for the direct and bridges methods, it would make the tests easier to read.

Done.


ios/MullvadRESTTests/TransportStrategyTests.swift line 171 at r1 (raw file):

Previously, buggmagnet wrote…

AnyIPAddress implements Equatable
No need for the rawValue checks here

Indeed

@mojganii mojganii requested a review from buggmagnet January 5, 2024 14:41
@mojganii mojganii force-pushed the use-various-methods-for-accessinng-to-api-ios-371 branch from 6c57fca to 9f9ff1c Compare January 6, 2024 11:49
Copy link
Contributor

@rablador rablador left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 2 of 6 files at r2, all commit messages.
Reviewable status: 2 of 19 files reviewed, 13 unresolved discussions (waiting on @buggmagnet and @mojganii)


ios/MullvadREST/Transport/TransportProvider.swift line 21 at r2 (raw file):

    private var currentTransport: RESTTransport?
    private let parallelRequestsMutex = NSLock()
    private var relayConstraints = RelayConstraints()

relayConstraints are not used anymore and can be removed.


ios/MullvadREST/Transport/TransportProvider.swift line 90 at r2 (raw file):

            do {
                let localConfiguration = try localShadowsocksLoader.configuration
                let config = configuration ?? localConfiguration

Nit: We don't need to try to load a local shadowsocks config unless incoming config is nil.


ios/MullvadREST/Transport/TransportStrategy.swift line 86 at r1 (raw file):

Previously, mojganii wrote…

fair enough.I doubt to get it done based on the discussion we had before on how to use extension

Doesn't really need to be an extension, just a public function.


ios/MullvadREST/Transport/TransportStrategy.swift line 131 at r2 (raw file):

        let fromData: (Data) -> UUID = { data in
            let str = String(data: data, encoding: .ascii)
            return UUID(uuidString: str!)!

Nit: These forced unwraps feel unsafe since incoming values are not guaranteed to be compatible. Sure, it's a private function, but I'm a little on the fence about it.

Copy link
Contributor

@rablador rablador left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 13 of 17 files at r1, 4 of 6 files at r2.
Reviewable status: all files reviewed, 16 unresolved discussions (waiting on @buggmagnet and @mojganii)


ios/MullvadRESTTests/AccessMethodRepositoryStub.swift line 12 at r2 (raw file):

typealias PersistentAccessMethod = MullvadSettings.PersistentAccessMethod
class AccessMethodRepositoryStub: AccessMethodRepositoryDataSource {

Nit: Could this be a struct with a single let accessMethods: [[PersistentAccessMethod] instead?


ios/MullvadRESTTests/TransportStrategyTests.swift line 107 at r2 (raw file):

            transportStrategy.didFail()
        }
        XCTAssertEqual(transportStrategy.connectionTransport(), .direct)

This should be moved into the loop instead, comparing the last and current values to make sure they're not the same.


ios/MullvadSettings/AppStorage.swift line 38 at r2 (raw file):

}

protocol AnyOptional {

Nit: Do we need an extension for this when AppStorage is the only place it's used?

@mojganii mojganii force-pushed the use-various-methods-for-accessinng-to-api-ios-371 branch 2 times, most recently from f3ac4c3 to 3ae100e Compare January 11, 2024 15:06
@mojganii mojganii requested a review from rablador January 11, 2024 15:24
Copy link
Collaborator Author

@mojganii mojganii left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 8 of 24 files reviewed, 15 unresolved discussions (waiting on @buggmagnet and @rablador)


ios/MullvadREST/Transport/TransportProvider.swift line 21 at r2 (raw file):

Previously, rablador (Jon Petersson) wrote…

relayConstraints are not used anymore and can be removed.

Done.


ios/MullvadREST/Transport/TransportProvider.swift line 90 at r2 (raw file):

Previously, rablador (Jon Petersson) wrote…

Nit: We don't need to try to load a local shadowsocks config unless incoming config is nil.

whenTransportStrategy fails to retrieve the configuration for bridges case,It iterates to the next one and preload new configuration for the next try attempt on bridges


ios/MullvadREST/Transport/TransportStrategy.swift line 86 at r1 (raw file):

Previously, rablador (Jon Petersson) wrote…

Doesn't really need to be an extension, just a public function.

the extension has been deleted


ios/MullvadREST/Transport/TransportStrategy.swift line 131 at r2 (raw file):

Previously, rablador (Jon Petersson) wrote…

Nit: These forced unwraps feel unsafe since incoming values are not guaranteed to be compatible. Sure, it's a private function, but I'm a little on the fence about it.

When we have default value for that and we are sure we're saving UUID it doesn't seem to me unsafe. after discussion Maroc, we came up to save string value od UUID like as before.


ios/MullvadRESTTests/AccessMethodRepositoryStub.swift line 12 at r2 (raw file):

Previously, rablador (Jon Petersson) wrote…

Nit: Could this be a struct with a single let accessMethods: [[PersistentAccessMethod] instead?

Done


ios/MullvadRESTTests/TransportStrategyTests.swift line 107 at r2 (raw file):

Previously, rablador (Jon Petersson) wrote…

This should be moved into the loop instead, comparing the last and current values to make sure they're not the same.

Done.


ios/MullvadSettings/AppStorage.swift line 38 at r2 (raw file):

Previously, rablador (Jon Petersson) wrote…

Nit: Do we need an extension for this when AppStorage is the only place it's used?

it's merged before,I've just moved it around.we can keep discussing out of the PR

Copy link
Contributor

@rablador rablador left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 2 of 12 files at r3, 7 of 13 files at r4, 7 of 7 files at r5, all commit messages.
Reviewable status: all files reviewed, 13 unresolved discussions (waiting on @buggmagnet and @mojganii)


ios/MullvadREST/Transport/AccessMethodIterator.swift line 19 at r5 (raw file):

    private var cancellables = Set<Combine.AnyCancellable>()

    /// `UserDefaults` key shared by both processes. Used to cache and synchronize last reachable api access method between them.

Nit: "Both processes", meaning packet tunnel and main app?


ios/MullvadREST/Transport/Shadowsocks/ShadowsocksLoader.swift line 43 at r5 (raw file):

    public func preloadNewConfiguration() throws {
        // because the previous shadowsocks configuration was invalid, therefore generate a new one.

Nit: Why was it invalid?


ios/MullvadREST/Transport/Shadowsocks/ShadowsocksLoader.swift line 55 at r5 (raw file):

        } catch {
            // There is no previous configuration either if this is the first time this code ran
            let newConfiguration = try create()

Nit: Reuse preloadNewConfiguration() instead?

Copy link
Contributor

@buggmagnet buggmagnet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 7 of 17 files at r1, 2 of 12 files at r3, 7 of 13 files at r4, 7 of 7 files at r5.
Reviewable status: all files reviewed, 16 unresolved discussions (waiting on @mojganii and @rablador)


ios/MullvadREST/Transport/AccessMethodIterator.swift line 19 at r5 (raw file):

Previously, rablador (Jon Petersson) wrote…

Nit: "Both processes", meaning packet tunnel and main app?

Yes


ios/MullvadREST/Transport/AccessMethodIterator.swift line 61 at r5 (raw file):

    private func refreshCacheIfNeeded() {
        /// Validating the index of `lastReachableApiAccessCache` after any changes in `AccessMethodRepository`
        if let idx = enabledConfigurations.firstIndex(where: { $0.id == self.lastReachableApiAccessId }) {

We can afford not having to use abbreviations, let's rename this firstIndex


ios/MullvadREST/Transport/TransportStrategy.swift line 95 at r5 (raw file):

}

private extension PersistentProxyConfiguration.SocksConfiguration {

This should be defined in SocksConfiguration instead.


ios/MullvadREST/Transport/Shadowsocks/ShadowsocksLoader.swift line 42 at r5 (raw file):

    }

    public func preloadNewConfiguration() throws {

We should rename this reloadConfiguration, and we can get rid of the comment, it doens't add information.


ios/MullvadREST/Transport/Shadowsocks/ShadowsocksLoader.swift line 54 at r5 (raw file):

            return try shadowsocksCache.read()
        } catch {
            // There is no previous configuration either if this is the first time this code ran

Half of the comment is missin here, the original comment was

// There is no previous configuration either if this is the first time this code ran
// Or because the previous shadowsocks configuration was invalid, therefore generate a new one.

ios/MullvadRESTTests/AccessMethodIteratorTests.swift line 14 at r5 (raw file):

import XCTest

final class AccessMethodIteratorTests: XCTestCase {

It looks like this class is missing tests ?


ios/MullvadRESTTests/TransportStrategyTests.swift line 33 at r1 (raw file):

Previously, mojganii wrote…

is not it a bit long? how about this :

It's better to be explicit about test names since usually they test should be testing one thing only


ios/MullvadRESTTests/TransportStrategyTests.swift line 58 at r1 (raw file):

Previously, mojganii wrote…

how about testReuseSameStrategyWhenEverythingElseIsDisabled

Sounds good


ios/MullvadRESTTests/TransportStrategyTests.swift line 159 at r5 (raw file):

    }
}

We are missing a couple of tests here

  • A test to make sure that when the bridges configuration is suggested, but fails to load a shadowsocks configuration, the next available method will be suggested
  • A test that verifies we do not enter an endless loop when only bridges API is selected, but we cannot load a shadowsocks configuration
  • A test for a socks5 configuration (that the configuration returns the correct username / password combo)

ios/MullvadVPNTests/APIAccessMethodsTests.swift line 29 at r5 (raw file):

        }
    }

Given what we found, let's add a test to make sure that when settings are wiped, the 2 default access methods are still available

@mojganii mojganii force-pushed the use-various-methods-for-accessinng-to-api-ios-371 branch from 3ae100e to 7b20832 Compare January 15, 2024 15:10
Copy link
Collaborator Author

@mojganii mojganii left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 11 of 25 files reviewed, 15 unresolved discussions (waiting on @buggmagnet and @rablador)


ios/MullvadREST/Transport/AccessMethodIterator.swift line 19 at r5 (raw file):

Previously, buggmagnet wrote…

Yes

yep


ios/MullvadREST/Transport/AccessMethodIterator.swift line 61 at r5 (raw file):

Previously, buggmagnet wrote…

We can afford not having to use abbreviations, let's rename this firstIndex

Done.


ios/MullvadREST/Transport/TransportStrategy.swift line 95 at r5 (raw file):

Previously, buggmagnet wrote…

This should be defined in SocksConfiguration instead.

Done.


ios/MullvadREST/Transport/Shadowsocks/ShadowsocksLoader.swift line 42 at r5 (raw file):

Previously, buggmagnet wrote…

We should rename this reloadConfiguration, and we can get rid of the comment, it doens't add information.

Done.


ios/MullvadREST/Transport/Shadowsocks/ShadowsocksLoader.swift line 43 at r5 (raw file):

Previously, rablador (Jon Petersson) wrote…

Nit: Why was it invalid?

when the relay is not available/reachable it should try with new configuration


ios/MullvadREST/Transport/Shadowsocks/ShadowsocksLoader.swift line 54 at r5 (raw file):

Previously, buggmagnet wrote…

Half of the comment is missin here, the original comment was

// There is no previous configuration either if this is the first time this code ran
// Or because the previous shadowsocks configuration was invalid, therefore generate a new one.

Done.


ios/MullvadREST/Transport/Shadowsocks/ShadowsocksLoader.swift line 55 at r5 (raw file):

Previously, rablador (Jon Petersson) wrote…

Nit: Reuse preloadNewConfiguration() instead?

reload just fetch and updates the cache


ios/MullvadRESTTests/TransportStrategyTests.swift line 33 at r1 (raw file):

Previously, buggmagnet wrote…

It's better to be explicit about test names since usually they test should be testing one thing only

Done.


ios/MullvadRESTTests/TransportStrategyTests.swift line 58 at r1 (raw file):

Previously, buggmagnet wrote…

Sounds good

Done.


ios/MullvadRESTTests/TransportStrategyTests.swift line 159 at r5 (raw file):

Previously, buggmagnet wrote…

We are missing a couple of tests here

  • A test to make sure that when the bridges configuration is suggested, but fails to load a shadowsocks configuration, the next available method will be suggested
  • A test that verifies we do not enter an endless loop when only bridges API is selected, but we cannot load a shadowsocks configuration
  • A test for a socks5 configuration (that the configuration returns the correct username / password combo)

Done.


ios/MullvadVPNTests/APIAccessMethodsTests.swift line 29 at r5 (raw file):

Previously, buggmagnet wrote…

Given what we found, let's add a test to make sure that when settings are wiped, the 2 default access methods are still available

I'll do that but it's out of this PR


ios/MullvadRESTTests/AccessMethodIteratorTests.swift line 14 at r5 (raw file):

Previously, buggmagnet wrote…

It looks like this class is missing tests ?

removed

Copy link
Contributor

@buggmagnet buggmagnet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 1 of 17 files at r1, 13 of 14 files at r6, all commit messages.
Reviewable status: 24 of 25 files reviewed, 7 unresolved discussions (waiting on @mojganii and @rablador)


ios/MullvadREST/Transport/TransportStrategy.swift line 82 at r6 (raw file):

        case let .socks5(configuration):
            switch configuration.authentication {
            case .noAuthentication:

nit
It's not a big deal, but ideally the TransportStrategy shouldn't care about how the SOCKS5 protocol works. (abstraction leaking)
The PersistentProxyConfiguration.usernamePassword should accept optional username and password instead, since the actual protocol negotiation happens in the SOCKS5 layer itself.

I will create a cleanup task to fix this.


ios/MullvadREST/Transport/Shadowsocks/ShadowsocksLoader.swift line 37 at r6 (raw file):

    public func reloadConfiguration() throws {
        // because the previous shadowsocks configuration was invalid, therefore generate a new one.

This comment doesn't really make sense here anymore, we should remove it.


ios/MullvadRESTTests/ShadowsocksLoaderStub.swift line 31 at r6 (raw file):

    @discardableResult
    func load() throws -> ShadowsocksConfiguration {
        if hasError {

This implementation has a couple of drawbacks:

  • You have to create the stub with an error by default, which doesn't make sense (a stub should not be in an error state by default, it should work immediately)
  • You are forced to pass an error in the constructor even if you don't need it

instead of having a variable named hasError you can make the error optional and do it this way instead (not that we use the compiler to automatically generate the constructor for us here)

struct ShadowsocksLoaderStub: ShadowsocksLoaderProtocol {
    let configuration: ShadowsocksConfiguration
    let error: Error?

    func reloadConfiguration() throws {
        try load()
    }

    @discardableResult
    func load() throws -> ShadowsocksConfiguration {
        if let error { throw error }
        return configuration
    }
}

Also this is the pattern we've used in the tests so far (see OutgoingConnectionProxyStub)


ios/MullvadRESTTests/TransportStrategyTests.swift line 258 at r6 (raw file):

}

private enum IOError: Error {

nit
If we just need an error without any cases, we can use an empty struct instead like so

private struct IOError: Error { }

ios/MullvadSettings/AccessMethodRepository.swift line 96 at r6 (raw file):

    }

    public func reloadWithDefaultsAfterDataRemoval() {

nit
To be honest, I'm not feeling very happy with how we are doing this, but it works for now so we can leave it.

However, I want us to change this in the future. (I don't like the precedent it sets of re-adding things after we wiped all the settings)

Copy link
Contributor

@buggmagnet buggmagnet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 24 of 25 files reviewed, 7 unresolved discussions (waiting on @mojganii and @rablador)


ios/MullvadREST/Transport/TransportStrategy.swift line 82 at r6 (raw file):

Previously, buggmagnet wrote…

nit
It's not a big deal, but ideally the TransportStrategy shouldn't care about how the SOCKS5 protocol works. (abstraction leaking)
The PersistentProxyConfiguration.usernamePassword should accept optional username and password instead, since the actual protocol negotiation happens in the SOCKS5 layer itself.

I will create a cleanup task to fix this.

The cleanup task is in IOS-455

@mojganii mojganii force-pushed the use-various-methods-for-accessinng-to-api-ios-371 branch from 7b20832 to 8d44697 Compare January 16, 2024 10:28
@mojganii mojganii requested a review from buggmagnet January 16, 2024 10:29
Copy link
Contributor

@rablador rablador left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 14 of 14 files at r6, 3 of 3 files at r7, all commit messages.
Reviewable status: all files reviewed, 6 unresolved discussions (waiting on @buggmagnet and @mojganii)


ios/MullvadRESTTests/ShadowsocksLoaderStub.swift line 31 at r6 (raw file):

Previously, buggmagnet wrote…

This implementation has a couple of drawbacks:

  • You have to create the stub with an error by default, which doesn't make sense (a stub should not be in an error state by default, it should work immediately)
  • You are forced to pass an error in the constructor even if you don't need it

instead of having a variable named hasError you can make the error optional and do it this way instead (not that we use the compiler to automatically generate the constructor for us here)

struct ShadowsocksLoaderStub: ShadowsocksLoaderProtocol {
    let configuration: ShadowsocksConfiguration
    let error: Error?

    func reloadConfiguration() throws {
        try load()
    }

    @discardableResult
    func load() throws -> ShadowsocksConfiguration {
        if let error { throw error }
        return configuration
    }
}

Also this is the pattern we've used in the tests so far (see OutgoingConnectionProxyStub)

+1


ios/MullvadRESTTests/TransportStrategyTests.swift line 196 at r6 (raw file):

    }

    func testUsesSocks5WithAuthenticationWhenItReaches() throws {

Nit: Reaches, as in makes a successful connection?


ios/MullvadSettings/AccessMethodRepository.swift line 96 at r6 (raw file):

Previously, buggmagnet wrote…

nit
To be honest, I'm not feeling very happy with how we are doing this, but it works for now so we can leave it.

However, I want us to change this in the future. (I don't like the precedent it sets of re-adding things after we wiped all the settings)

If we're simply adding the defaults again I think we can use add() instead of adding another function for it.

Copy link
Contributor

@buggmagnet buggmagnet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: all files reviewed, 6 unresolved discussions (waiting on @mojganii and @rablador)


ios/MullvadSettings/AccessMethodRepository.swift line 96 at r6 (raw file):

Previously, rablador (Jon Petersson) wrote…

If we're simply adding the defaults again I think we can use add() instead of adding another function for it.

I think what Mojgan did here is clearer from the API name instead of just calling add, the intent is stronger IMHO.

Copy link
Collaborator Author

@mojganii mojganii left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: all files reviewed, 6 unresolved discussions (waiting on @buggmagnet and @rablador)


ios/MullvadREST/Transport/TransportStrategy.swift line 82 at r6 (raw file):

Previously, buggmagnet wrote…

The cleanup task is in IOS-455

Good.


ios/MullvadREST/Transport/Shadowsocks/ShadowsocksLoader.swift line 37 at r6 (raw file):

Previously, buggmagnet wrote…

This comment doesn't really make sense here anymore, we should remove it.

Done.


ios/MullvadRESTTests/ShadowsocksLoaderStub.swift line 31 at r6 (raw file):

Previously, buggmagnet wrote…

This implementation has a couple of drawbacks:

  • You have to create the stub with an error by default, which doesn't make sense (a stub should not be in an error state by default, it should work immediately)
  • You are forced to pass an error in the constructor even if you don't need it

instead of having a variable named hasError you can make the error optional and do it this way instead (not that we use the compiler to automatically generate the constructor for us here)

struct ShadowsocksLoaderStub: ShadowsocksLoaderProtocol {
    let configuration: ShadowsocksConfiguration
    let error: Error?

    func reloadConfiguration() throws {
        try load()
    }

    @discardableResult
    func load() throws -> ShadowsocksConfiguration {
        if let error { throw error }
        return configuration
    }
}

Also this is the pattern we've used in the tests so far (see OutgoingConnectionProxyStub)

Done.


ios/MullvadRESTTests/TransportStrategyTests.swift line 196 at r6 (raw file):

Previously, rablador (Jon Petersson) wrote…

Nit: Reaches, as in makes a successful connection?

it means when the rotation reaches to Sock5


ios/MullvadRESTTests/TransportStrategyTests.swift line 258 at r6 (raw file):

Previously, buggmagnet wrote…

nit
If we just need an error without any cases, we can use an empty struct instead like so

private struct IOError: Error { }

the enum case makes more readable


ios/MullvadSettings/AccessMethodRepository.swift line 96 at r6 (raw file):

Previously, buggmagnet wrote…

I think what Mojgan did here is clearer from the API name instead of just calling add, the intent is stronger IMHO.

me either,Maybe we shouldn't remove the defaults in resetting process or returning/updating the defaults in fetchAll

Code snippet:

   public func fetchAll() -> [PersistentAccessMethod] {
        let accessMethods = (try? readApiAccessMethods()) ?? []
        if accessMethods.isEmpty {
            add([direct,bridge]) // or return defaults
        }
        return accessMethods
    }

@mojganii mojganii self-assigned this Jan 16, 2024
Copy link
Contributor

@buggmagnet buggmagnet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 2 of 3 files at r7, all commit messages.
Reviewable status: all files reviewed, 2 unresolved discussions (waiting on @mojganii and @rablador)


ios/MullvadSettings/AccessMethodRepository.swift line 96 at r6 (raw file):

Previously, mojganii wrote…

me either,Maybe we shouldn't remove the defaults in resetting process or returning/updating the defaults in fetchAll

This has been discussed offline

Copy link
Contributor

@buggmagnet buggmagnet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM !

Reviewable status: all files reviewed, 2 unresolved discussions (waiting on @mojganii and @rablador)

@buggmagnet buggmagnet force-pushed the use-various-methods-for-accessinng-to-api-ios-371 branch from 8d44697 to fcc9bf9 Compare January 16, 2024 13:20
@buggmagnet buggmagnet merged commit 03031d9 into main Jan 16, 2024
4 of 5 checks passed
@buggmagnet buggmagnet deleted the use-various-methods-for-accessinng-to-api-ios-371 branch January 16, 2024 13:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
iOS Issues related to iOS
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants